﻿
using EmperialApps.WeatherSpark.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace EmperialApps.WeatherSpark {

    public static class Equal {

#pragma warning disable 1720
        public static readonly IAssertion<Forecast> Location = default( Forecast ).AssertValue( f => f.Location );

        public static readonly IAssertion<Forecast> Description = default( Forecast ).AssertValue( f => f.Description );

        public static readonly IAssertion<Forecast> Start = default( Forecast ).AssertValue( f => f.Start );

        public static readonly IAssertion<Forecast> StartOffset = default( Forecast ).AssertValue( f => f.Start.Offset );

        public static readonly IAssertion<Forecast> Interval = default( Forecast ).AssertValue( f => f.Interval );

        public static readonly IAssertion<Forecast> Temperature = default( Forecast ).AssertValue( f => f.Temperature );

        public static readonly IAssertion<Forecast> PrecipitationPotential = default( Forecast ).AssertValue( f => f.PrecipitationPotential );

        public static readonly IAssertion<Forecast> SkyCover = default( Forecast ).AssertValue( f => f.SkyCover );

        public static readonly IAssertion<Forecast> RelativeHumidity = default( Forecast ).AssertValue( f => f.RelativeHumidity );

        public static readonly IAssertion<Forecast> WindSpeed = default( Forecast ).AssertValue( f => f.WindSpeed );

        public static readonly IAssertion<Forecast> WindDirection = default( Forecast ).AssertValue( f => f.WindDirection );

        public static readonly IAssertion<Forecast> Pressure = default( Forecast ).AssertValue( f => f.Pressure );


        public static readonly IAssertion<Place> PlaceLocation = default( Place ).AssertValue( p => p.Location );

        public static readonly IAssertion<Place> PlaceCityName = default( Place ).AssertValue( p => p.CityName );

        public static readonly IAssertion<Place> PlaceCounty = default( Place ).AssertValue( p => p.County );

        public static readonly IAssertion<Place> PlaceLink = default( Place ).AssertValue( p => p.Link );

        public static readonly IEqualityComparer<Place> PlaceComparer = new AssertionEqualityComparer<Place>( PlaceLocation, PlaceCityName, PlaceCounty, PlaceLink );
#pragma warning restore 1720


        public static void AssertEqual( this Forecast actual, Forecast expected ) {
            actual.AssertAll( expected, Location, Description, Start, StartOffset, Interval, Temperature, PrecipitationPotential, SkyCover, RelativeHumidity, WindSpeed, WindDirection, Pressure );
        }

        public static void AssertEqual( this Place actual, Place expected ) {
            actual.AssertAll( expected, PlaceLocation, PlaceCityName, PlaceCounty, PlaceLink );
        }


        public static IAssertion<T> AssertValue<T, U>( this T inference, Expression<Func<T, U>> getValue ) {
            return new EqualAssert<T, U>( getValue );
        }


        private sealed class EqualAssert<T, U> : IAssertion<T> {

            private readonly string _name;
            private readonly Func<T, U> _getValue;

            public EqualAssert( Expression<Func<T, U>> getValue ) {
                this._name = getValue.Body.ToString( );
                this._getValue = getValue.Compile( );
            }

            public void Assert( T actual, T expected ) {
                U actualValue = this._getValue( actual );
                U expectedValue = this._getValue( expected );

                try {
                    Xunit.Assert.Equal( expectedValue, actualValue );
                }
                catch( Xunit.Sdk.EqualException ex ) {
                    throw new Exception( Environment.NewLine + "[" + this._name + "]", ex );
                }
            }

            public bool Equals( T x, T y ) {
                U xValue = this._getValue( x );
                U yValue = this._getValue( y );

                bool equal = EqualityComparer<U>.Default.Equals( xValue, yValue );
                return equal;
            }

            public int GetHashCode( T obj ) {
                U value = this._getValue( obj );
                return EqualityComparer<U>.Default.GetHashCode( value );
            }

            public override string ToString( ) {
                return this._name;
            }

        }

        private sealed class AssertionEqualityComparer<T> : IEqualityComparer<T> {

            private readonly IAssertion<T>[] _assertions;

            public AssertionEqualityComparer( params IAssertion<T>[] assertions ) {
                this._assertions = assertions;
            }

            public bool Equals( T x, T y ) {
                bool equal = this._assertions.All( a => a.Equals( x, y ) );
                return equal;
            }

            public int GetHashCode( T obj ) {
                return this._assertions
                    .Select( a => a.GetHashCode( obj ) )
                    .Aggregate( ( hash, assertionHash ) => ((hash << 5) + hash) ^ assertionHash );
            }

        }

    }

}
